package com.binarycube.webserver;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.NativeJSON;
import org.mozilla.javascript.NativeJavaArray;
import org.mozilla.javascript.NativeJavaObject;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;

import com.binarycube.webserver.config.ScriptConfig;
import com.binarycube.webserver.config.ServerConfig;
import com.binarycube.webserver.datasource.DataManager;
import com.binarycube.webserver.session.ISessionManager;
import com.binarycube.webserver.session.Session;
import com.binarycube.webserver.wrappers.WrappedRequest;
import com.binarycube.webserver.wrappers.WrappedResponse;
import com.binarycube.webserver.wrappers.WrappedSession;

public class ScriptHandler extends AbstractHandler {

	static Logger log = Logger.getLogger(ScriptHandler.class.getName());

	ServerConfig serverConfig;

	public ScriptHandler(ServerConfig config) {
		serverConfig = config;
	}

	@Override
	public void handle(String target, final Request baseRequest,
			HttpServletRequest request, HttpServletResponse response)
			throws IOException, ServletException {
		// Server wide response settings

		response.setCharacterEncoding("UTF-8");
		// Wrap the request for processing
		WrappedRequest wrappedRequest = new WrappedRequest(request);
		WrappedResponse wrappedResponse = new WrappedResponse(response);

		ISessionManager sm = serverConfig.getSessionManager();
		Session session = sm.getSession(wrappedRequest);
		if (session == null) session = sm.createSession(wrappedRequest);

		sm.persistSession(wrappedResponse, session);

		try {

			ScriptConfig scriptConfig = serverConfig.getScript(target.substring(1));

			if (scriptConfig == null) {
				// Not found error
				response.sendError(HttpStatus.NOT_FOUND_404,
						"Script not found.");
				return;
			}

			if ((scriptConfig.isPrivate()) && (!session.isAuthenicated())) {
				// Security error
				response.sendError(HttpStatus.UNAUTHORIZED_401,
						"You are unathorized to access this script.");
				return;
			}

			//See if we are viewing the source
			if (wrappedRequest.hasParameter("viewSource")) {
				//If so, return the source code for the script and exit
				
				String template = FileUtils.readFileToString(new File("etc/ScriptView.txt"));
				String source = FileUtils.readFileToString(new File(serverConfig.getScriptBase() + File.separatorChar + scriptConfig.getSource()));
				response.getWriter().print(template.replace("{script}", source));
				response.setStatus(HttpServletResponse.SC_OK);
				response.flushBuffer();
				baseRequest.setHandled(true);
				return;
			}
			
			
			if ((scriptConfig != null) && (scriptConfig.getSource() != null)) {

				Context cx = Context.enter();
				try {
					Scriptable scope = cx.initStandardObjects();

					// Add ability for scripts to log errors
					Object jsOut = Context.javaToJS(log, scope);
					ScriptableObject.putProperty(scope, "log", jsOut);
					//And add access to wrapped request and response
					Object jsRequest = Context.javaToJS(wrappedRequest, scope);
					ScriptableObject.putProperty(scope, "request", jsRequest);
					Object jsResponse = Context.javaToJS(wrappedResponse, scope);
					ScriptableObject.putProperty(scope, "response", jsResponse);
					
					Object jsSession = Context.javaToJS(new WrappedSession(session), scope);
					ScriptableObject.putProperty(scope, "session", jsSession);
										
					Object jsDSM = Context.javaToJS(DataManager.getInstance(), scope);
					ScriptableObject.putProperty(scope, "data", jsDSM);
					
					
				
					FileReader reader = new FileReader(	serverConfig.getScriptBase() + File.separatorChar + scriptConfig.getSource());
					Object result = cx.evaluateReader(scope, reader, "<cmd>",1, null);
					//Check if there is something to return to the browser
					if (!(result instanceof org.mozilla.javascript.Undefined)) {
						// Add the result to the response
						String tmp = renderResult(response, cx, scope, result);
						response.getWriter().write(tmp);
					}
					reader.close();
				} finally {
					Context.exit();
				}
				response.setStatus(HttpServletResponse.SC_OK);
				response.flushBuffer();
				baseRequest.setHandled(true);

			} else {
				// Not found
				response.setStatus(HttpServletResponse.SC_NOT_FOUND);
				baseRequest.setHandled(true);
			}

		} catch (Exception e) {
			log.error("Error executing script: ", e);
			response.sendError(
					HttpStatus.INTERNAL_SERVER_ERROR_500,
					"Oops, there has been a problem in the server:"
							+ e.getLocalizedMessage());
		}

	}

	
	private String renderResult(HttpServletResponse response,Context cx,Scriptable scope, Object result){
		String tmp = "";
		if (result != null) {
			//Render primitives as string
			if ((result instanceof String) || (result instanceof Integer) || (result instanceof Double) ) 	{
				tmp = result.toString();	
			}else if (result instanceof NativeJavaArray) {
				boolean first = true;
				for (Object x : (Object[])((NativeJavaArray)result).unwrap()) {
					if (first) {
						tmp += renderResult(response, cx, scope, x);
						first = false;
					} else {
						tmp += "," + renderResult(response, cx, scope, x);
					}
				}
				
			} else if (result instanceof NativeJavaObject){
				// Java objects - call toString();
				tmp = ((NativeJavaObject) result).unwrap().toString();
			} else {
				//Otherwise render to JSON
				response.setContentType("text/javascript");
				tmp = (String) NativeJSON.stringify(cx, scope, result, null, null);
			}
				
		}
		return tmp;
	}
	
	
}
